#include "p24FJ256GB110.h"
#include "Mikro.h"
#include "MikroSPI.h"
#include "M25P80.h"

#define M25P80_CS (_LATC2)

/* Instruction Set */
#define M25P80_WREN      (0b00000110)	// write enable
#define M25P80_WRDI      (0b00000100)	// write disable
#define M25P80_RDSR      (0b00000101)	// read status register
#define M25P80_WRSR      (0b00000001)	// write status register
#define M25P80_READ      (0b00000011)	// read data bytes
#define M25P80_FAST_READ (0b00001011)	// fast read data bytes
#define M25P80_PP        (0b00000010)	// page program
#define M25P80_SE        (0b11011000)	// sector erase
#define M25P80_BE        (0b11000111)	// bulk erase
#define M25P80_DP        (0b10111001)	// deep power-down
#define M25P80_RES       (0b10101011)	// release from deep power-down; read electronic signature

/* Status register bitmasks */
#define M25P80_SR_SRWD        (0b10000000)	// status register write protect
#define M25P80_SR_BP2         (0b00010000)	// block protected bit 2
#define M25P80_SR_BP1         (0b00001000)	// block protected bit 1
#define M25P80_SR_BP0         (0b00000100)	// block protected bit 0
#define M25P80_SR_WEL         (0b00000010)	// write enabled latch bit
#define M25P80_SR_WIP         (0b00000001)	// write in progress bit
#define M25P80_SR_DEFAULT     (0b00000000)	// no protection mode

// Chip select logic
#define CS_DESELECT (1)
#define CS_SELECT (0)

void write_enable();
void write_status_register(uint8 SR_val);
void wait_for_WIP();
void wait_for_WEL();

/* Initialize the M25P80 flash memory device */
void M25P80_Init(void)
{
	// set up chip select
	_TRISC2 = 0;
	_LATC2 = CS_DESELECT;

	// settle clock at correct idle level with a dummy write
	SPIwrite(0xff);

	// disable protection mode
	write_status_register(M25P80_SR_DEFAULT);
}

/* Erase entire memory (set all bits to 1). (TAKES AROUND 10 SECONDS)
*
* The Bulk Erase (BE) instruction is executed only if all Block Protect
* (BP2, BP1, BP0) bits are 0. The Bulk Erase (BE) instruction is ignored if
* one, or more, sectors are protected.
*/
void M25P80_bulk_erase()
{
	write_enable();

	// Bulk Erase sequence
	M25P80_CS = CS_SELECT;
	SPIwrite(M25P80_BE);
	M25P80_CS = CS_DESELECT;

	// Wait for bulk erase to finish
	M25P80_CS = CS_SELECT;
	wait_for_WIP();
	M25P80_CS = CS_DESELECT;
}

/* Erase an entire sector (set all bits to 1). */
void M25P80_sector_erase(uint8 sectorNumber)
{
	if (sectorNumber > M25P80_TOTAL_SECTORS) {
		return;		// invalid sector number
	}

	write_enable();

	// Sector Erase sequence
	M25P80_CS = CS_SELECT;
	SPIwrite(M25P80_SE);
	// serialize sector address
	SPIwrite(sectorNumber);
	SPIwrite(0);
	SPIwrite(0);
	M25P80_CS = CS_DESELECT;

	// Wait for WIP unset
	M25P80_CS = CS_SELECT;
	wait_for_WIP();
	M25P80_CS = CS_DESELECT;
}

/* Program num_bytes bytes to M25P80 memory starting at address pageAddr
 * using the data in write_data.
 *
 * Precede this function by clearing the page being written to (via M25P80_sector_erase() or
 * M25P80_bulk_erase()) because bits are cleared only to 1 and written only to 0.
 */
void M25P80_write(uint32 pageAddr, uint16 num_bytes, uint8 * write_data)
{
	uint16 byte, page = 0;
	uint32 num_pages = num_bytes / M25P80_BYTES_PER_PAGE + ((num_bytes % M25P80_BYTES_PER_PAGE != 0) ? (1) : (0));	/*integer division truncates, add page for remainder */

	while (page < num_pages) {	// write one page at a time
		write_enable();
		uint32 byteAddr = page * M25P80_BYTES_PER_PAGE;
		M25P80_CS = CS_SELECT;
		// Page Program sequence
		SPIwrite(M25P80_PP);
		// Serialize and send address
		SPIwrite((uint8) ((byteAddr >> 16) & 0xff));
		SPIwrite((uint8) ((byteAddr >> 8) & 0xff));
		SPIwrite((uint8) (byteAddr & 0xff));
		for (byte = page * M25P80_BYTES_PER_PAGE;
		     byte <
		     ((num_bytes <
		       (page +
			1) *
		       M25P80_BYTES_PER_PAGE) ? (num_bytes) : ((page +
								1) *
							       M25P80_BYTES_PER_PAGE));
		     byte++) {
			SPIwrite(write_data[byte]);
		}
		M25P80_CS = CS_DESELECT;

		M25P80_CS = CS_SELECT;
		wait_for_WIP();	// wait for this page write to finish
		M25P80_CS = CS_DESELECT;
		page++;
	}
}

/* Read num_bytes bytes from M25P80 memory starting at memory location addr (which
 * is not limited to a page boundary) and store it in array read_data.
 * 
 * When the highest address is reached, the address counter rolls
 * over to 000000h, allowing the read sequence to be continued indefinitely.
 */
void M25P80_read(uint32 addr, uint16 num_bytes, uint8 * read_data)
{
	// TODO bounds-checking
	uint16 i;

	M25P80_CS = CS_SELECT;
	SPIwrite(M25P80_READ);
	// serialize address
	SPIwrite((uint8) ((addr >> 16) & 0xff));
	SPIwrite((uint8) ((addr >> 8) & 0xff));
	SPIwrite((uint8) (addr & 0xff));
	for (i = 0; i < num_bytes; i++) {
		read_data[i] = SPIwrite(0xff);
	}
	M25P80_CS = CS_DESELECT;	// break READ sequence
}

/* Set the M25P80 status register WEL bit to program or erase */
void write_enable()
{
	// Write enable sequence
	M25P80_CS = CS_SELECT;
	SPIwrite(M25P80_WREN);
	M25P80_CS = CS_DESELECT;
	M25P80_CS = CS_SELECT;
	wait_for_WIP();
	wait_for_WEL();
	M25P80_CS = CS_DESELECT;
}

/* Write a value to the M25P80 status register. */
void write_status_register(uint8 SR_val)
{
	write_enable();

	// Write Status Register sequence
	M25P80_CS = CS_SELECT;
	SPIwrite(M25P80_WRSR);
	SPIwrite(SR_val);
	M25P80_CS = CS_DESELECT;

	// Wait for status register to finish being written
	M25P80_CS = CS_SELECT;
	wait_for_WIP();
	M25P80_CS = CS_DESELECT;
}

/* Wait for M25P80 Write In Progress bit to be unset */
void wait_for_WIP()
{
	uint8 statusReg;
	SPIwrite(M25P80_RDSR);
	do {
		statusReg = SPIwrite(0xff);
	} while (statusReg & M25P80_SR_WIP);
}

/* Wait for M25P80 Write Enable latch to be set */
void wait_for_WEL()
{
	uint8 statusReg;
	SPIwrite(M25P80_RDSR);
	do {
		statusReg = SPIwrite(0xff);
	} while (!(statusReg & M25P80_SR_WEL));
}

// TODO M25P80_fast_read()
